import {storeKey} from "@/vuex/injectKey";
import ModuleCollection from "@/vuex/module/module-collection";
import {forEachValue, isPromise} from "@/vuex/utils";
import {reactive, watch} from "vue";

export class Store {
  constructor(options) {
    const store = this;

    // 格式化用户的参数 后续使用方便些
    // options 就是 createStore 传递的 options (查看 src/store/index.js)
    // 长这样: { state,actions,mutations,getter,modules }
    // 我们现在想把这个数据的结构变得好看一些 现在的数据结构看不出父子关系 (emmm... 虽然 .modules 下面就是子模块
    // 我们希望它变成这样: ↓
    /*
      root = {
        _raw: rootModule,
        state: rootModule.state,
        _children:{
          aCount: {
            _raw: aModule,
            state: aModule.state,
            _children:{
              cCount: {
                _raw: cModule,
                state:cModule.state,
                _children:{}
              }
            }
          },
          bCount: {
            _raw: bModule,
            state: bModule.state,
            _children:{}
          }
        }
      }
      root.state aCount.state bCount.state
    */
    this._modules = new ModuleCollection(options);
    console.log('this._modules:', this._modules)

    // {add:[fn,fn,fn]} 发布订阅模式
    store._wrappedGetters = Object.create(null);
    store._mutations = Object.create(null);
    store._actions = Object.create(null);

    // 是不是严格模式
    this.strict = options.strict || false;

    this._committing = false;

    // 我们希望通过installModule进行安装后能这样访问:
    // store.state.aCount.cCount.count
    const state = store._modules.root.state;
    installModule(store, state, [], store._modules.root);
    console.log('installModule安装过后的根state:', state);
    /*
      installModule安装过后的根state:
      {count: 1, aCount: {…}, bCount: {…}}
        aCount:
          cCount: {count: 0}
          count: 0
          __proto__: Object
        bCount: {count: 0}
        count: 1
        __proto__: Object

      除此之外还会对store._wrappedGetters、store._mutations、store._actions进行安装
      没有设置命名空间的情况下 相同的方法(mutations、actions)会声明在一起
      store: Store {_modules: ModuleCollection, _wrappedGetters: {…}, _mutations: {…}, _actions: {…}}
              _actions: {asyncAdd: Array(1)}
              _modules: ModuleCollection {root: Module}
              _mutations: {add: Array(4)}
              _wrappedGetters: {double: ƒ}
              __proto__: Object
    */

    resetStoreState(store, state);
    /*
      此时会往 store
        + getters 属性
        + _state 属性: store._state = reactive({data: state})
    */

    store._subscribes = [];
    options.plugins.forEach(plugin => plugin(store));
  }

  subscribe(fn){
    this._subscribes.push(fn);
  }

  get state() {
    return this._state.data;
  }

  replaceState(newState){
    // 严格模式下 不能直接修改状态
    this._withCommit(() => {
      this._state.data = newState;
    });
  }

  install(app, injectKey) {
    app.provide(injectKey || storeKey, this);

    //↓等价于: Vue.prototype.$store = this
    app.config.globalProperties.$store = this;
  }

  commit = (type, payload) => {
    const entry = this._mutations[type] || [];

    // entry && entry.forEach(handler => handler(payload));
    this._withCommit(() => {
      entry && entry.forEach(handler => handler(payload));
    });

    //store.subscribe((mutation,state) => {...})
    this._subscribes.forEach(sub => sub({type, payload}, this.state));
  };

  dispatch = (type, payload) => {
    const entry = this._actions[type] || [];
    return Promise.all(entry.map(handler => handler(payload)));
  };

  _withCommit(fn){
    const committing = this._committing;
    this._committing = true;
    fn(); // 这里会改变state 就会同步触发执行 watch的 cb
    this._committing = committing;
  }

  registerModule(path, rawModule) {
    const store = this;
    if(typeof path === 'string') path = [path];

    // 要在原有的模块基础上新增加一个
    const newModule = store._modules.register(rawModule, path);

    // 再把模块安装上
    installModule(store, store.state, path, newModule);

    // 重置
    resetStoreState(store,store.state)
  }
}

function installModule(store, rootState, path, module) {
  const isRoot = !path.length; //如果数组是空数组 说明是根

  const namespaceStr = store._modules.getNamespaced(path);
  console.log('namespaceStr:',namespaceStr);

  if (!isRoot) {
    let parentState = path.slice(0, -1).reduce((state, key) => state[key], rootState);
    store._withCommit(()=>{
      parentState[path[path.length - 1]] = module.state;
    })
  }

  module.forEachGetter((getter, key) => {
    store._wrappedGetters[namespaceStr + key] = () => {
      // return getter(module.state); // 如果直接使用模块上自己的状态, 此状态不是响应式的 (懒代理, 要一层层.下去 才会递归代理 才具备响应式 收集能力)
      return getter(getNestedState(store.state, path));
    };
  })

  module.forEachMutation((mutation, key) => {
    const entry = store._mutations[namespaceStr + key] || (store._mutations[namespaceStr + key] = [])

    //mutation ---> add(state,payload){...}
    //store.commit('add',payload)
    entry.push((payload) => {
      mutation.call(store, getNestedState(store.state, path), payload);
    });
  })

  module.forEachAction((action, key) => {
    const entry = store._actions[namespaceStr + key] || (store._actions[namespaceStr + key] = [])

    entry.push((payload) => {
      const res = action.call(store, store, payload);
      if (!isPromise(res)) {
        return Promise.resolve(res);
      }
      return res;
    });
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child);
  });
}

function getNestedState(state, path) { //根据路径 获取store上面的最新状态
  return path.reduce((state, key) => state[key], state);
}

function resetStoreState(store, state) {
  store._state = reactive({data: state}); //store._state.data = 'xxx'
  const wrappedGetters = store._wrappedGetters;
  store.getters = {};
  forEachValue(wrappedGetters, (getter, key) => {
    Object.defineProperty(store.getters, key, {
      get: getter,
      enumerable: true
    });
  });

  if(store.strict){
    enableStrictMode(store);
  }
}

function enableStrictMode(store){
  watch(() => store._state.data, () => {
    console.assert(store._committing/*不是true,就会走后面的*/, 'do not mutate vuex store state outside mutation handlers');
  }, {deep: true, flush: 'sync'}); // 默认watchApi是异步的, 这里改成同步的监控
}
